package jmine.tec.utils.reflection.fast.asm;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import jmine.tec.utils.reflection.fast.AbstractFastClass;
import jmine.tec.utils.reflection.fast.FastClass;
import jmine.tec.utils.reflection.fast.FastClassFactory;
import jmine.tec.utils.reflection.fast.FastConstructor;
import jmine.tec.utils.reflection.fast.FastMethod;
import jmine.tec.utils.reflection.fast.reflection.ReflectionFastClassFactory;

/**
 * @author takeshi
 */
public class CachedCompiledFastClassFactory extends FastClassFactory {

    private final FastMethodCompiler methodCompiler;

    private final FastConstructorCompiler ctorCompiler;

    private final FastClassFactory original = new ReflectionFastClassFactory();

    private final ConcurrentMap<Class<?>, FastClass> cache = new ConcurrentHashMap<Class<?>, FastClass>();

    /**
     * @param classLoader {@link OpenClassLoader}
     */
    public CachedCompiledFastClassFactory(OpenClassLoader classLoader) {
        super();
        this.methodCompiler = new FastMethodCompiler(classLoader);
        this.ctorCompiler = new FastConstructorCompiler(classLoader);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public FastClass forClass(Class<?> type) {
        FastClass fc = this.cache.get(type);
        if (fc != null) {
            return fc;
        }
        synchronized (type) {
            // one more lookup, now holding the lock
            fc = this.cache.get(type);
            if (fc != null) {
                return fc;
            }
            FastClass forClass = this.original.forClass(type);
            final Collection<? extends FastMethod> methods = forClass.getMethods();
            List<FastMethod> cms = new ArrayList<FastMethod>(methods.size());
            for (FastMethod fastMethod : methods) {
                cms.add(this.methodCompiler.compileAndLoad(fastMethod));
            }
            Collection<? extends FastConstructor> constructors = forClass.getConstructors();
            List<FastConstructor> ccs = new ArrayList<FastConstructor>(constructors.size());
            for (FastConstructor fastConstructor : constructors) {
                ccs.add(this.ctorCompiler.compileAndLoad(fastConstructor));
            }
            fc = new FastClassImpl(cms, ccs, type);
            FastClass stored = this.cache.putIfAbsent(type, fc);
            if (stored == null) {
                return fc;
            } else {
                return stored;
            }
        }
    }

    /**
     * Implementacao trivial
     * 
     * @author takeshi
     */
    private static final class FastClassImpl extends AbstractFastClass {

        private final List<FastMethod> methods;

        private final List<FastConstructor> ctors;

        private final Class<?> type;

        /**
         * @param methods List
         * @param ctors List
         * @param type Class
         */
        public FastClassImpl(List<FastMethod> methods, List<FastConstructor> ctors, Class<?> type) {
            super();
            this.type = type;
            this.methods = methods;
            this.ctors = ctors;
        }

        /**
         * {@inheritDoc}
         */
        public Collection<? extends FastConstructor> getConstructors() {
            return new ArrayList<FastConstructor>(this.ctors);
        }

        /**
         * {@inheritDoc}
         */
        public Collection<? extends FastMethod> getMethods() {
            return new ArrayList<FastMethod>(this.methods);
        }

        /**
         * {@inheritDoc}
         */
        public Class<?> getType() {
            return this.type;
        }

    }

}
